//////////////////////////////////////////////////////////////////////////////
//
//  Created by Philip Mulcahy on 30/01/2012.
//  Copyright (c) 2012 Philip Mulcahy. All rights reserved.
//
//  This file is part of the note-recog library.
//
//  note-recog is free software: you can redistribute it and/or modify
//  it under the terms of version 3 of the GNU Lesser General Public License 
//  as published by the Free Software Foundation.
//
//  note-recog is distributed is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with note-recog.  If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////

#include "Condenser.h"
#include "Slice.h"
#include "NoteEvent.h"
#include "IConsumer.h"
#include "Harmonic.h"
#include <cassert>
#include <QuartzCore/CABase.h>
#include "Log.h"
#include "Frame.h"

using namespace std;

LOG_DEFINE(Condenser);

Condenser::Condenser( IConsumer * consumer ) :
    _consumer(consumer),
    _decayingRmsEnvelope(0.f),
    _lastRms(0.f),
    _lastTime(0.),
    _lastRiseTime(0.),
    _lastNoteSentTime(0.),
    _eventCount(0),
    _initInfo(),
    _edgeDetector(new EdgeDetector(_edge_listener, 0)),
    _pitchDetector(new PitchDetector(_pitch_listener))
{
}

Condenser::Condenser( IConsumer * consumer, const InitInfo& info ) :
    _consumer(consumer),
    _decayingRmsEnvelope(0.f),
    _lastRms(0.f),
    _lastTime(0.),
    _lastRiseTime(0.),
    _lastNoteSentTime(0.),
    _eventCount(0),
    _initInfo(info),
    _edgeDetector(new EdgeDetector(_edge_listener, 0)),
    _pitchDetector(new PitchDetector(_pitch_listener))
{
}

void Condenser::submit( const Slice& slice )
{
    LOG_DEBUG( "got " << slice );
    
    const Frame frame(
        slice.timeDomain(),
//        slice.spectrum(),
        slice.startSeconds(),
        slice.spectrum().samplingFreq());
    
    _pitchDetector->submitFrame(frame);
    _edgeDetector->submitFrame(frame);
    
    const float rms = slice.rmsLevel();
    const double time = slice.startSeconds();
    const float deltaT = time - _lastTime;
    const float decayFactor = exp( -deltaT / _initInfo.decayTimeConstant() );
    const bool envelopeRise = rms > _decayingRmsEnvelope;
    
    assert( 0.f < decayFactor && decayFactor <= 1.f );
    
    ++_eventCount;
    _decayingRmsEnvelope = decayFactor * _decayingRmsEnvelope + (1.f - decayFactor) * rms;
    _decayingRmsEnvelope = max( _decayingRmsEnvelope, rms );
    _lastTime = time;
    _lastRms = rms;
    if( envelopeRise )
    {
        LOG_DEBUG( "rose to " << _decayingRmsEnvelope << " at " << time );
        _lastRiseTime = time;
    }
    
    const float timeSinceRise = time - _lastRiseTime;
    const float timeSinceNote = time - _lastNoteSentTime;
    const bool noteSentThisFall = timeSinceRise > timeSinceNote;
    if( ! noteSentThisFall )
    {
        const bool inTransient = timeSinceRise < _initInfo.transientBlackoutDuration();
        if( !inTransient )
        {
            const float spectrumquality = slice.spectrum().quality();
            const bool sufficientquality = spectrumquality >= _initInfo.minQuality();
            if( sufficientquality )
            {
                const float freq = slice.fundamental().freq();
                const NoteEvent evt(freq,time);
                if( _consumer )
                {
                    _consumer->onNoteRecognized(evt);
                }
                _lastNoteSentTime = time;
            }
        }
    }
    
    if( _eventCount % 10 == 0 )
    {
        LOG_DEBUG( *this );
    }
}


// Slow params: Optimiser::single_input{decayTimeConstant=0.142383,minQuality=0.919196,transientBlackoutDuration=0.36991}
// Fast params: Optimiser::single_input{decayTimeConstant=0.0203125,minQuality=2.27813,transientBlackoutDuration=0.0195265}
// Both params: Optimiser::single_input{decayTimeConstant=0.0451823,minQuality=2.27813,transientBlackoutDuration=0.0195265}
Condenser::InitInfo::InitInfo()
: _transientBlackoutDuration(0.37f),
  _minQuality(0.9f),
  _decayTimeConstant(0.014f)
{}

const float& Condenser::InitInfo::transientBlackoutDuration() const
{
    return _transientBlackoutDuration;
}

const float& Condenser::InitInfo::minQuality() const
{
    return _minQuality;
}

const float& Condenser::InitInfo::decayTimeConstant() const
{
    return _decayTimeConstant;
}

void Condenser::InitInfo::setTransientBlackoutDuration(const float& value) 
{ 
    _transientBlackoutDuration = value;
}

void Condenser::InitInfo::setMinQuality(const float& value)
{
    _minQuality = value;
}

void Condenser::InitInfo::setDecayTimeConstant(const float& value)
{
    _decayTimeConstant = value;
}

ostream& operator<<(ostream& os, const Condenser& condenser)
{
    os.precision(12);
    os
    << "Condenser{time:" << condenser._lastTime
    << ",lastRms:" << condenser._lastRms
    << ",envelope:" << condenser._decayingRmsEnvelope
    << ",events:" << condenser._eventCount
    << "}";
    
    return os;
}
